#!/usr/bin/python3
import codecs
import csv
from http.client import CONTINUE
import os
from pickle import FALSE
import requests
import shlex
import subprocess
from requests.auth import HTTPBasicAuth
from lxml import etree
from retrying import retry
from src.libs.logger import logger
from src.config import constant
from src.libs.base import http
from src.libs.base import save2csv
from src.libs.executecmd import ExecuteCmd
from src.community.openeuler_community_info_query import OpenEulerCommunityRepoInfoQuery

EXCLUDED_RPM_INFO = ["debugsource", "debuginfo", "src.rpm", "debug"]
TARGET_RPM_INFO = ["src.rpm"]
# EXCLUDED_RPM_INFO = ["debugsource", "debuginfo", "src.rpm", "debug"]
BAD_STATUS = ['finished', 'failed', 'unresolvable']
VALID_ARCHES = ["x86_64", "aarch64", "loongarch64"]

class OpensuseObsAPI:
    """
    opensuse obs中的相关接口操作
    """
    
    def __init__(self, project=None, account=None, password=None) -> None:
        self._project = project
        self._account = account
        self._password = password
        self.file_size = 1024 * 20
        
    def _url(self, url, **kwargs):
        return constant.OBS_HOST + url.format(**kwargs)

    def _meta(self, package, project):
        meta = """<package name="{package}" project="{project}">
                <title />
                <description />
              </package>""".format(
            package=package, project=project
        )
        return meta

    @property
    def _auth(self):
        return HTTPBasicAuth(self._account, self._password)

    def get_source_package_of_project(self, project, sig_info_needed=False):
        """
        @description :获取指定project的package列表
        ----------- 
        @param :

        -----------
        @returns :
        -----------
        """
        package_list = []
        if not project:
            logger.warning("No projects to be queryed")
            return package_list

        result_csv_name ='_'.join([project[0].replace(':', '_'), 'source', '.csv'])
        sig = OpenEulerCommunityRepoInfoQuery()

        for pj in project:
            url = self._url(url="source/{pj}", pj=pj)

            response = http.get(url, auth=self._auth)
            if response.status_code != 200:
                logger.error(f"{response}")
                continue

            etree_element = etree.HTML(response.text)
            try:
                all_entry = etree_element.xpath("//entry")
                for entry in all_entry:
                    name = entry.xpath("./@name")[0]
                    __, spec_name, __ = sig.get_gitee_repo_related_name('gitee_repo', name)
                    if sig_info_needed:
                        sig_name = sig.get_sig_name(name)
                    else:
                        sig_name = 'NA'
                    package_list.append([name, spec_name, pj, sig_name])
            except (TypeError, IndexError) as err:
                logger.warning("analyse {url} failed: {err}")
                continue
                
        save2csv(result_csv_name, 'w+', ['Source Package', 'specname', 'Project', 'sig_name'], package_list)
        return package_list

    def _parse_build_result(self, project, etree_element, pkgs, sig_info_query):
        """
        prase the build result,especcially the details of bad packages

        Args:
            project: project name
            etree_element: root node of project's _result html
            pkgs: list of bad packages
            sig_info_query: object of OpenEulerCommunityRepoInfoQuery

        Returns:
            _builds: dict of statistics of packages's status
            _bad_details: record of detail of bad packages

        """
        
        _builds = dict() # record num of different status
        _bad_details = [] # record detail of package, which status is bad
        all_result = etree_element.xpath("//result") # get all the 'result'

        num = 0
        for arch_result in all_result:
            arch = arch_result.xpath("./@arch")[0]
            logger.info(f"current arch is {arch}")

            if arch not in VALID_ARCHES:
                continue

            for status in arch_result.xpath("./status"): # get all 'status' nodes of current arch
                package = status.xpath("./@package")[0]
                code = status.xpath("./@code")[0]
                _builds[arch] = dict()
                try:
                    _builds[arch][code].append(package)
                except KeyError:
                    _builds[arch][code] = [package]

                if package in pkgs:
                    continue

                # get currecnt status of pacckage：succeed/failed/unresolvable/blocked/finished
                if code not in BAD_STATUS:
                    continue
                
                pkg_detail = []
                if code in {'finished', 'unresolvable'} :
                    details = status.xpath("./details/text()")
                    if not details or details[0] == 'succeeded':
                        continue
                    else:
                        pkg_detail = [provides.strip() for provides in details]

                pkgs.append(package)
                sig_name = sig_info_query.get_sig_name(package)
                _bad_details.append([package, project, code, arch, pkg_detail, sig_name])
                num += 1
                
                logger.info(f"catch {num}: {package} {code}  {arch}")

        return _builds, _bad_details


    def get_bad_packages(self, project, branch = None):
        """
        get all of packaegs, which status are unresolvable/failed
        Args:
            project: specific project
            branch: specific branch, decides the project from mapped table
            check_code: status list: 'finished', 'failed', 'unresolvable'
        Returns:
        
        """
        if branch:
            project = constant.GITEE_BRANCH_PROJECT_MAPPING[branch] # Get the mapped project list
            sig_info_query = OpenEulerCommunityRepoInfoQuery(branch)
            res_csv_name = '_'.join([branch, 'problematic_package.csv'])
        else:
            sig_info_query = OpenEulerCommunityRepoInfoQuery()
            res_csv_name = 'problematic_package.csv'
    
        pkgs = []
        result_data = []
        for pj in project:
            url = self._url(url="build/{project}/_result", project=pj)
            logger.info(url)
            response = http.get(url, auth=self._auth)

            if response.status_code != 200:
                logger.error(f"response.status_code: {response.status_code}")
                return None
            
            project_etree_element = etree.HTML(response.text)
            __, _bad_details = self._parse_build_result(pj, project_etree_element, pkgs, sig_info_query)
            result_data.extend(_bad_details)

        save2csv(res_csv_name, 'w', ["Package", "Project", "Problem_type", "Arch", "Detail", "Sig"], result_data)
        return

    def upload_file(self, package, path, project=None):
        """
        上传文件
        """
        if not project:
            project = self._project
        if os.path.isfile(path):
            files = {path.split("/")[-1]: path}
        else:
            files = {
                file.split("/")[-1]: os.path.join(path, file)
                for file in os.listdir(path)
                if os.path.isfile(os.path.join(path, file))
            }
        for file_name, file in files.items():
            url = self._url(
                url="source/{project}/{package}/{filename}",
                project=project,
                package=package,
                filename=file_name,
            )
            try:
                response = requests.put(
                    url,
                    auth=self._auth,
                    data=open(file, "rb"),
                )
                if response.status_code != 200:
                    raise requests.exceptions.HTTPError

            except Exception as error:
                print("上传软件包%s的文件失败:%s \n" % (package, file))

        print("\n")

    def create_package(self, package, project=None):
        """
        创建软件包
        """
        if not project:
            project = self._project
        url = self._url(
            url="source/{project}/{package}/_meta", project=project, package=package
        )
        meta = self._meta(package, project).encode("utf-8")
        response = requests.put(
            url,
            auth=self._auth,
            data=meta,
            timeout=15,
        )
        return response.status_code == 200

    def exists_package(self, package, project=None):
        """
        判断指定工程下是否存在特定的软件包
        """
        if not project:
            project = self._project
        url = self._url(
            url="source/{project}/{package}", project=project, package=package
        )
        response = http.get(url, auth=self._auth)
        if response.status_code == 404:
            return False
        if response.status_code == 200 and package in response.text:
            return True

    

    def _project_meta(self, project):
        _meta = """<project name="{project}">
            <title />
            <description />
            <person userid="{account}" role="maintainer" />
            <debuginfo>
                <enable />
            </debuginfo>
            <repository name="standard_x86_64">
                <path project="openEuler:20.03:LTS:SP2:Epol" repository="standard_x86_64"/>
                <arch>x86_64</arch>
            </repository>
            <repository name="standard_aarch64">
                <path project="openEuler:20.03:LTS:SP2:Epol" repository="standard_aarch64"/>
                <arch>aarch64</arch>
            </repository>
            </project>
        """.format(
            project=project, account=self._account
        )
        return _meta

    def create_project(self, project):
        """
        创建工程
        """
        if not project:
            project = self._project
        url = self._url(url="source/{project}/_meta", project=project)
        meta = self._project_meta(project).encode("utf-8")
        response = requests.put(
            url,
            auth=self._auth,
            data=meta,
            timeout=15,
        )
        return response.status_code == 200

    def exists_project(self, project):
        """
        判断指定工程是否存在
        """
        url = self._url(url="source/{project}", project=project)
        response = http.get(url, auth=self._auth)
        if response.status_code == 404:
            return False
        if response.status_code == 200:
            return True

    def get_source(self):
        """
        获取所有的工程
        """
        project = []
        response = http.get(constant.OBS_OWN_HOME, auth=self._auth)
        if response.status_code == 200:
            etree_element = etree.HTML(response.text)
            project = [
                project
                for project in etree_element.xpath(
                    "//*[@id='involved-projects-table']/tbody/tr/td/a/text()"
                )
            ]
        return project
    

    def download_binary_file(
        self,
        project,
        package,
        binary_name,
        respository="standard_aarch64",
        arch="aarch64",
        save_path=None,
    ):
        """
        下载二进制rpm包
        """
        url = self._url(
            url="build/{project}/{repository}/{arch}/{package}/{binaryname}",
            project=project,
            repository=respository,
            arch=arch,
            package=package,
            binaryname=binary_name,
        )
        response = http.get(url, auth=self._auth)
        if response.status_code != 200:
            return False
        try:
            if save_path is None:
                save_path = os.path.join(constant.INSTALL_PACKAGE_PATH, "check-install")
            if not os.path.exists(save_path):
                os.makedirs(save_path, exist_ok=True)
            with open(os.path.join(save_path, binary_name), "wb") as file:
                file.write(response.content)
            return True
        except IOError:
            return False

    def published_packages(
        self, project, package, targets = TARGET_RPM_INFO, repository="standard_aarch64", arch="aarch64"):
        """
        发布的软件包
        """
        url = self._url(
            url="build/{project}/{repository}/{arch}/{package}",
            project=project,
            package=package,
            repository=repository,
            arch=arch,
        )

        response = http.get(url, auth=self._auth)
        if response.status_code != 200:
            logger.error(f"{response}")
            return None

        etree_element = etree.HTML(response.text)
        try:
            filenames = [
                file
                for file in etree_element.xpath("//binary/@filename")
                if all(
                    [
                        trait in file for trait in targets
                    ]
                )
                and file.endswith(".rpm")
            ]
            return filenames[-1]
        except (TypeError, IndexError):
            return None

    def trigger_build(self, project, package=None, code=None, arch=None):
        """
        @description :触发单个软件包或整个工程的编译
        -----------
        @param :
        -----------
        @returns :
        -----------
        """
        if project is None:
            logger.warning("no project parameter")
            return False

        url = self._url(url="build/{project}?cmd=rebuild", project=project)
        data = dict()
        if package:
            data["package"] = package
        if code:
            data["code"] =code
        if arch:
            data["arch"] = arch

        response = requests.post(url, auth=self._auth, data=data)
        logger.info(response)

        if response.status_code == 200:
            logger.info(f'build project: {project}, code: {code}, arch:{arch} sucessfully!')

        return response.status_code == 200

    def trigger_service(self, project, package=None):
        """
        @description :同步gitee上对应branch的代码到OBS的project
        -----------
        @param :
        -----------
        @returns :
        -----------
        """
        pkgs = []
        if package:
            pkgs.extend(package)
        else:
            oscls_cmd = f"osc ls {project}"
            oscls_cmd_out = ExecuteCmd.cmd_output(oscls_cmd.split())
            if  oscls_cmd_out is None:
                logger.error(f"{oscls_cmd} failed")
                return
            pkgs = oscls_cmd_out.split("\n")

        with codecs.open("runservice_failed.csv", "a+", 'utf-8') as result_file:
            writer = csv.writer(result_file)
            writer.writerow(["Project", "Package"])

            for pkgname in pkgs:
                data_list = []
                data_list.append(project)
                data_list.append(pkgname)
                logger.info(f"runservice {pkgname}")
                sync_cmd = f"osc service remoterun {project} {pkgname}"
                sync_cmd_split = shlex.split(sync_cmd)
                try:
                    sync_cmd_out = ExecuteCmd.cmd_status(sync_cmd_split)
                except subprocess.TimeoutExpired as err:
                    logger.error(f"{sync_cmd} failed: {err}")
                    writer.writerow(data_list)
                    continue
                if  sync_cmd_out:
                    logger.error(f"{sync_cmd} failed")
                    writer.writerow(data_list)
                    continue